using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Text.RegularExpressions;

using Serialiser = System.Runtime.Serialization.Formatters.Binary.BinaryFormatter;

namespace TienLen
{

    /// <summary>
    /// Class containing customisable text information.
    /// </summary>
    class FontInfo : ICloneable
    {
        private Color fontColour = Color.Black;
        private Color endfontColour = Color.Black;

        private Color bgColour = SystemColors.Window;
        private Color endbgColour = SystemColors.Window;

        private FontStyle style;
        private string text;

        /// <summary>
        /// The colour of the text.
        /// </summary>
        public Color ForeColor
        {
            get { return fontColour; }
            set { fontColour = value; }
        }

        /// <summary>
        /// [Gradient] The ending colour of the text.
        /// </summary>
        public Color EndForeColor
        {
            get { return endfontColour; }
            set { endfontColour = value; }
        }

        public string Text
        {
            get { return text; }
            set { text = value; }
        }

        /// <summary>
        /// The back colour of the background.
        /// </summary>
        public Color Background
        {
            get { return bgColour; }
            set { bgColour = value; }
        }

        /// <summary>
        /// [Gradient] The ending colour of the background.
        /// </summary>
        public Color EndBackground
        {
            get { return endbgColour; }
            set { endbgColour = value; }
        }

        /// <summary>
        /// The font style of the text.
        /// </summary>
        public FontStyle Font
        {
            get { return style; }
            set { style = value; }
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }

    /// <summary>
    /// Class which can handle parsing the MSN Plus! text formats.
    /// </summary>
    class MSNPlusName
    {
        private List<FontInfo> values = new List<FontInfo>();
        private string text = string.Empty;

        /// <summary>
        /// The list of text values.
        /// </summary>
        public List<FontInfo> Values
        {
            get { return values; }
        }

        /// <summary>
        /// Text without the MSN Plus! formatting.
        /// </summary>
        public string Text
        {
            get { return text; }
        }

        public MSNPlusName(string text)
        {
            XmlDocument xmlDoc = new XmlDocument();
            
            #region 1. Parse into XMLDocument

            if (text.Contains("[") && text.Contains("]"))
            {
                string temp = text;

                // http://www.w3schools.com/xml/xml_syntax.asp - Only the & and < characters are illegal inside a XML element
                temp = temp.Replace("<", "&lt;");
                temp = temp.Replace("&", "&amp;");

                while (temp != Regex.Replace(temp, @"\[([buis])\](.*?)\[\/\1]", "<$1>$2</$1>"))
                    temp = Regex.Replace(temp, @"\[([buis])\](.*?)\[\/\1]", "<$1>$2</$1>");

                // Accept only colour values of integers or in HTML format (#XXXXXX).
                while (temp != Regex.Replace(temp, @"\[([ca])=(\d+|#\w+)\](.*?)\[\/\1(?:=(\d+|#\w+)|)]", "<$1 col='$2' end='$4'>$3</$1>"))
                    temp = Regex.Replace(temp, @"\[([ca])=(\d+|#\w+)\](.*?)\[\/\1(?:=(\d+|#\w+)|)]", "<$1 col='$2' end='$4'>$3</$1>");

                // If the the text remains unchanged, consider it unsuccessful.
                if (text == temp)
                    return;

                xmlDoc.PreserveWhitespace = true;
                xmlDoc.LoadXml("<MC>" + temp + "</MC>");
            }
            else
                return;

            #endregion

            #region 2. Get the values

            XmlElement core = xmlDoc["MC"];

            for (int i = 0; i < core.ChildNodes.Count; i++)
            {
                GetValue(core.ChildNodes[i], new FontInfo());
            }

            #endregion
        }

        private void AppendText(FontInfo value)
        {
            if (value.Text.Contains("&lt;"))
                value.Text = value.Text.Replace("&lt;", "<");

            text += value.Text;
            values.Add(value);
        }

        private void GetValue(XmlNode element, FontInfo fontInfo)
        {
            FontInfo info = fontInfo;
            bool ret = true;

            // Text and no formatting
            if (element.NodeType == XmlNodeType.Text)
            {
                info.Text = element.Value;
                AppendText(info);
            }

        VALPROCESS:

            for (int i = 0; i < element.ChildNodes.Count; i++)
            {
                // Example: [b][/b] - No value inside with no sub-formatting
                if (element.ChildNodes[i].ChildNodes.Count == 0 && element.ChildNodes[i].Value == null)
                    continue;

                // If the child node has a value (and not a dat node), set it as the text.
                else if (!ret && element.ChildNodes[i].NodeType == XmlNodeType.Text)
                {
                    info.Text = element.ChildNodes[i].Value;
                    AppendText(info);

                    continue;
                }

#if DEBUG
                XmlNode node = element.ChildNodes[i];
#endif

                FontInfo tempFont = (!ret && element.ChildNodes[i].ChildNodes.Count == 1 && element.ChildNodes[i].FirstChild.NodeType == XmlNodeType.Text) ?
                    (FontInfo)info.Clone() : null;

                // We won't need to set the value of variable info, since the it is referenced :)
                SetFontInfo(ret ? element : element.ChildNodes[i], tempFont != null ? tempFont : info);

                // Once we're done looking at the main node, now look at the child nodes.
                if (ret)
                {
                    if (element.ParentNode.Name == "MC" && element.ChildNodes.Count == 0)
                        values.Add(tempFont != null ? tempFont : info);

                    ret = false;
                    goto VALPROCESS;
                }

                // If one child node has more than one sub-nodes, look into each sub-node in that child node.
                if (element.ChildNodes[i].ChildNodes.Count > 0)
                {
                    for (int j = 0; j < element.ChildNodes[i].ChildNodes.Count; j++)
                    {
                        GetValue(element.ChildNodes[i].ChildNodes[j], tempFont != null ? tempFont : (FontInfo)info.Clone());
                    }
                }
            }
        }

        private void SetFontInfo(XmlNode element, FontInfo info)
        {
            // <c> or <a> child node
            if (element.NodeType != XmlNodeType.Text && element.Attributes.Count > 0)
            {
                for (int i = 0; i < element.Attributes.Count; i++)
                {
                    if (string.IsNullOrEmpty(element.Attributes[i].Value))
                        continue;

                    string value = element.Attributes[i].Value;
                    int res;
                    Color colorUse;


                    if (int.TryParse(value, out res))
                        colorUse = GetMSNColor(res);
                    else
                        // #000000 will give an alpha value of 0, thus it will be treated as white.
                        colorUse = Color.FromArgb(255, ColorTranslator.FromHtml(value.Substring(1, 6)));

                    if (element.Name == "c")
                    {
                        if (i == 0)
                            info.ForeColor = colorUse;
                        else
                            info.EndForeColor = colorUse;
                    }
                    else
                    {
                        if (i == 0)
                            info.Background = colorUse;
                        else
                            info.EndBackground = colorUse;
                    }
                }
            }
            else
            {
                switch (element.Name.ToLower())
                {
                    case "b":

                        info.Font |= FontStyle.Bold;
                        break;

                    case "i":

                        info.Font |= FontStyle.Italic;
                        break;

                    case "s":

                        info.Font |= FontStyle.Strikeout;
                        break;

                    case "u":

                        info.Font |= FontStyle.Underline;
                        break;
                }
            }
        }

        /// <summary>
        /// Get the colour based on the number.
        /// </summary>
        /// <remarks>[c/a = (num)]</remarks>
        private Color GetMSNColor(int num)
        {
            // Thanks to http://web.njit.edu/~kevin/rgb.txt.html for providing possible colour names.
            switch (num)
            {
                default:
                case 1:
                    return Color.Black;

                case 0:
                    return Color.White;

                case 2:
                    return Color.Navy;

                case 3:
                    return Color.FromArgb(0, 147, 0);

                case 4:
                    return Color.Red;

                case 5:
                    return Color.Maroon;

                case 6:
                    return Color.FromArgb(156, 0, 156);

                case 7:
                    return Color.FromArgb(252, 127, 0);

                case 8:
                    return Color.Yellow;

                case 9:
                    return Color.FromArgb(0, 252, 0);

                case 10:
                    return Color.FromArgb(0, 147, 147);

                case 11:
                    return Color.Aqua;
                    
                case 12:
                    return Color.FromArgb(32, 32, 252);

                case 13:
                    return Color.Fuchsia;

                case 14:
                    return Color.FromArgb(127, 127, 127);

                case 15:
                    return Color.FromArgb(210, 210, 210);

                case 16:
                    return Color.FromArgb(231, 230, 228);

                case 17:
                    return Color.FromArgb(207, 205, 208);

                case 18:
                    return Color.FromArgb(255, 222, 164);

                case 19:
                    return Color.FromArgb(255, 174, 185);

                case 20:
                    return Color.FromArgb(255, 168, 255);

                case 21:
                    return Color.FromArgb(180, 180, 252);

                case 22:
                    return Color.FromArgb(186, 251, 229);

                case 23:
                    return Color.FromArgb(193, 255, 163);

                case 24:
                    return Color.FromArgb(250, 253, 162);

                case 25:
                    return Color.FromArgb(182, 180, 183);

                case 26:
                    return Color.FromArgb(162, 160, 161);

                case 27:
                    return Color.FromArgb(249, 193, 82);

                case 28:
                    return Color.FromArgb(255, 109, 102);

                case 29:
                    return Color.FromArgb(255, 98, 255);

                case 30:
                    return Color.FromArgb(108, 108, 255);

                case 31:
                    return Color.FromArgb(104, 255, 195);

                case 32:
                    return Color.FromArgb(142, 255, 103);

                case 33:
                    return Color.FromArgb(249, 255, 87);

                case 34:
                    return Color.FromArgb(133, 132, 130);

                case 35:
                    return Color.FromArgb(110, 109, 123);

                case 36:
                    return Color.FromArgb(255, 160, 30);

                case 37:
                    return Color.FromArgb(249, 38, 17);

                case 38:
                    return Color.FromArgb(255, 32, 255);

                case 39:
                    return Color.FromArgb(32, 43, 255);

                case 40:
                    return Color.FromArgb(30, 25, 165);

                case 41:
                    return Color.FromArgb(96, 249, 19);

                case 42:
                    return Color.FromArgb(255, 248, 19);

                case 43:
                    return Color.FromArgb(94, 100, 100);

                case 44:
                    return Color.FromArgb(75, 73, 76);

                case 45:
                    return Color.FromArgb(217, 136, 18);

                case 46:
                    return Color.FromArgb(235, 5, 5);

                case 47:
                    return Color.FromArgb(222, 0, 222);

                case 48:
                    return Color.FromArgb(0, 0, 211);

                case 49:
                    return Color.FromArgb(3, 204, 136);

                case 50:
                    return Color.FromArgb(89, 216, 13);

                case 51:
                    return Color.FromArgb(212, 200, 4);

                case 52:
                    return Color.FromArgb(51, 53, 53);

                case 53:
                    return Color.FromArgb(24, 23, 28);

                case 54:
                    return Color.FromArgb(148, 78, 0);

                case 55:
                    return Color.FromArgb(155, 0, 8);

                case 56:
                    return Color.FromArgb(152, 2, 153);

                case 57:
                    return Color.FromArgb(1, 3, 140);

                case 58:
                    return Color.FromArgb(1, 136, 95);

                case 59:
                    return Color.FromArgb(56, 150, 0);

                case 60:
                    return Color.FromArgb(154, 158, 21);

                case 61:
                    return Color.FromArgb(71, 52, 0);

                case 62:
                    return Color.FromArgb(77, 0, 0);

                case 63:
                    return Color.FromArgb(95, 1, 98);

                case 64:
                    return Color.FromArgb(0, 0, 71);

                case 65:
                    return Color.FromArgb(6, 80, 47);

                case 66:
                    return Color.FromArgb(28, 83, 0);

                case 67:
                    return Color.FromArgb(84, 77, 5);
            }
        }

        /// <summary>
        /// Get a boolean value if the parse was successful.
        /// </summary>
        public bool Success
        {
            get { return values.Count != 0; }
        }
    }
}
